#! /usr/bin/env python3
# _*_ coding: utf8 _*_
import os
import shutil
import sys
import math
import codecs
import argparse
from ftplib import FTP_TLS, error_perm, all_errors, FTP
from tqdm import tqdm
from glob import glob
import logging
import coloredlogs

coloredlogs.install(level='INFO')
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s",
                    datefmt='%Y-%m-%d  %H:%M:%S %a')

# 解决中文乱码
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())


class LinuxFtp(object):
    def __init__(self, host, user, password, remote_dir, enption):
        """
        带SSL证书的ftp 连接初始化
        :param host: ftp登录用户
        :param user: ftp远程地址
        :param password: ftp登录密码
        :param remote_dir: ftp远程目录
        :param enption: 是否加密连接
        """
        if enption:
            self.ftps = FTP_TLS(host, timeout=100)
            self.ftps.prot_p()  # 启用加密通道
        else:
            self.ftps = FTP(host, timeout=100)
        self.ftps.login(user, password)
        self.ftp_makedirs_cwd(remote_dir)  # 切换ftp目录，不存在则创建

    def ftp_makedirs_cwd(self, path, first_call=True):
        """
        连接ftp 指定目录，不存在则创建该目录，ftplib必须连接并登录
        :param path: ftp远程目录
        :param first_call:  True
        :return: 无
        """
        try:
            self.ftps.cwd(path)
        except error_perm:
            logging.warning(u'无远程目录 %s' % path)
            self.ftps.mkd(path)
            logging.info(u'已创建远程目录 %s ' % path)
            if first_call:
                self.ftps.cwd(path)

    def Absolute_path(self, file_path):
        """
        检验路径是否为绝对路径
        :param file_path: 上传文件目录地址
        :return:
        """
        # 判断是否为绝对路径
        if os.path.isabs(file_path):
            file = os.path.basename(os.path.normpath(file_path))
            path_dir = file_path.split(file)[0]
            os.chdir(path_dir)
            self.UploadFile(file)
        else:
            self.UploadFile(file_path)

    def UploadFile(self, filename):
        """
        上传单个文件，通过tqdm实现可视化上传进度条并返回
        :param filename: 上传文件名称
        :return: 无
        """
        try:
            for _ in range(1):
                filesize = os.path.getsize(filename)
                logging.info(u'开始上传 %s' % filename)
                with open(filename, 'rb') as fd:
                    with tqdm(unit='B', unit_scale=True, leave=False, miniters=1, desc='Uploading......',
                              total=filesize)as t:
                        def callback_(data):
                            length = len(data)
                            t.update(length)

                        self.ftps.storbinary('STOR {}'.format(filename), fd, 1024, callback_)
                logging.info(u'文件: %s 已上传完毕' % filename)
        except KeyboardInterrupt:
            logging.warning('Ctrl C 中断退出')

    def DownloadFile(self, filename):
        """
        下载ftp文件到当前位置,通过tqdm实现可视化下载进度条并返回
        :param filename:
        :return:
        """
        try:
            logging.info(u'开始下载 %s' % filename)
            with open(filename, 'wb') as fd:
                total = self.ftps.size(filename)
                with tqdm(unit='B', unit_scale=True, leave=False, miniters=1,
                          desc='Downloading......', total=total) as pbar:
                    def callback_(data):
                        length = len(data)
                        pbar.update(length)
                        fd.write(data)

                    self.ftps.retrbinary('RETR {}'.format(filename), callback_)
            logging.info(u'文件: %s 已下载完毕' % filename)
        except error_perm:
            logging.error('服务器并无此文件，或目标目录不正确!')
        except all_errors as e:
            logging.error(u'ERROR: %s ' % e)
        except KeyboardInterrupt:
            logging.warning('Ctrl C 中断退出')

    def __del__(self):
        # 退出FTP 连接
        self.ftps.close()
        logging.info('已断开连接')


# 可视化文件大小
def converBytes(bytes, lst=['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']):
    i = int(math.floor(  # 舍弃小数点，取小
        math.log(bytes, 1024)  # 求对数(对数：若 a**b = N 则 b 叫做以 a 为底 N 的对数)
    ))

    if i >= len(lst):
        i = len(lst) - 1
    return ('%.2f' + " " + lst[i]) % (bytes / math.pow(1024, i))


# 对目标进行压缩
def make_gzfile(filename):
    if os.path.isabs(filename):
        file = os.path.basename(os.path.normpath(filename))
        path_dir = filename.split(file)[0]
        logging.info(u'正在压缩 %s' % file)
        shutil.make_archive(filename, 'gztar', path_dir, file)
        gz_file = str(filename) + ".tar.gz"
    else:
        path_dir = os.getcwd()
        logging.info(u'正在压缩 %s' % filename)
        shutil.make_archive(u'%s/%s' % (path_dir, filename), 'gztar', path_dir, filename)
        gz_file = str(filename) + ".tar.gz"

    gz_size = os.path.getsize(gz_file)
    logging.info(f'目标压缩完毕，压缩后大小为: {converBytes(gz_size)}')
    return gz_file


# 判断文件类型
def file_type(filename, compression=False):
    if os.path.exists(filename):
        if os.path.isfile(filename):
            if compression:
                compressed_lst = ['gz', 'tar', 'zip', 'rar']
                if filename.split('.')[-1] in compressed_lst:
                    return filename
                logging.info(u'开始压缩文件{}'.format(filename))
                return make_gzfile(filename)
            else:
                return filename
        if os.path.isdir(filename):
            return make_gzfile(filename)


# 连接ftp
def ftp_login(**kwargs):
    ftp_args = kwargs['kwargs']
    try:
        logging.info(u"初始化连接")
        Ftpes = LinuxFtp(ftp_args['host'], ftp_args['user'], ftp_args['password'], ftp_args['remotedir'],
                         ftp_args['encryption'])
        return Ftpes
    except all_errors as e:
        logging.error('ftp connection error: {}'.format(e))
        exit(1)


# 定义帮助信息
def parse_args():
    parser = argparse.ArgumentParser(description='ftpes connect')
    parser.add_argument('-f', '--filename',
                        dest='filename',
                        default='',
                        type=str,
                        help='上传文件名称,支持正则表达式')
    parser.add_argument('-r', '--remotedir',
                        dest='remotedir',
                        type=str,
                        help="""指定ftp远程目录,不存在则创建""")
    args = parser.parse_args()
    return args


def main():
    args = parse_args()
    ftp_args = {'host': '192.168.50.134',
                'user': r'admin',
                'password': 'admin123',
                'remotedir': args.remotedir,
                'encryption': False,  # 是否启用加密连接，默认不启用
                'sparse': False,  # 是否转换上下载，默认为上传操作
                'compression': False,  # 是否对文件开启压缩，默认不压缩
                }

    Ftpes = ftp_login(kwargs=ftp_args)
    if args.filename != '':
        file_list = glob(str(args.filename))
        logging.info(u'匹配到文件: %s' % file_list)
        for file in file_list:
            filename = file_type(file, ftp_args['compression'])
            try:
                if ftp_args['sparse']:
                    Ftpes.DownloadFile(args.filename)
                else:
                    Ftpes.Absolute_path(filename)
            except Exception as e:
                logging.error('ftp error {}'.format(e))
    else:
        logging.error(u'必须输入相关连接信息,详情参阅 -h')


if __name__ == '__main__':
    main()
